﻿using DGZImp.Core.Extensions;
using DGZImp.Core.Services;
using DGZImp.MdsProject.Helper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Nomad.Iot;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;

namespace DGZImp.MdsProject.Devices.Base
{
    public class WebSocketServe : IIotServe
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public Uri Url { get; set; }

        /// <summary>
        /// 是否自动读取数据
        /// </summary>
        public bool IsAutoRead { get; set; } = true;

        private ClientWebSocket Client { get; set; }

        /// <summary>
        /// 读取间隔，单位ms
        /// </summary>
        public int ReadInterval { get; set; } = 2000;

        public string RequestModel { get; set; }

        public event PlcDataReceiveHandler OnPlcDataReceive;
        public event HttpDataReceiveHandler OnHttpDataReceive;
        public event TcpDataReceiveHandler OnTcpDataReceive;
        public event WebSocketDataReceiveHandler OnWebSocketDataReceive;

        public string SessionId { get; set; } = null;

        private int ReceiveBufferSize { get; set; } = 1024;
        private readonly CancellationTokenSource _cts = new CancellationTokenSource();

        private SemaphoreSlim semaphore = new(1, 1);

        public WebSocketServe(string id, string name, string url, bool isAutoRead = true, int readInterval = 2000, string requestModel = null)
        {
            Id = id;
            Name = name;
            Url = new Uri(url);
            IsAutoRead = isAutoRead;
            ReadInterval = readInterval;
            RequestModel = requestModel;
            StartReadingAsync().ConfigureAwait(false);
        }

        private async Task GetSession()
        {
            try
            {
                var r = await ScadaHelper.GetSession();
                SessionId = r;
            }
            catch (Exception e)
            {
                SysLogger.LogError(e, "WebSocket获取Session失败");
            }
        }

        public async Task StartReadingAsync()
        {
            while (!_cts.IsCancellationRequested)
            {
                var datas = new ConcurrentDictionary<string, byte[]>();
                await Task.Delay(ReadInterval, _cts.Token);
                if (!IsAutoRead)
                {
                    continue;
                }
                if (RequestModel.IsNullOrEmpty())
                {
                    continue;
                }
                try
                {
                    var r = await SendRequestAsync(RequestModel);
                    if (r.IsSuccess)
                    {
                        OnWebSocketDataReceive?.Invoke(this, r.Content);
                    }
                    else
                    {
                        SysLogger.LogError($"WS服务{Url.ToString()}读取数据异常：{r.Message}");
                        OnWebSocketDataReceive?.Invoke(this, null);
                    }
                }
                catch (Exception e)
                {
                    SysLogger.LogError(e, $"WS服务{Url.ToString()}读取数据失败");
                    OnWebSocketDataReceive?.Invoke(this, null);
                }
            }
        }

        private async Task ConnectAsync()
        {
            try
            {
                if (Client == null) 
                {
                    Client = new ClientWebSocket();
                    await Client.ConnectAsync(Url, _cts.Token);
                }
                else if (Client.State != WebSocketState.Open)
                {
                    SysLogger.LogError($"WS服务连接状态{Client.State.ToString()}");
                    await Client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, _cts.Token);
                    Client.Dispose();
                    Client = new ClientWebSocket();
                    await Client.ConnectAsync(Url, _cts.Token);
                }
            }
            catch (Exception e)
            {
                SysLogger.LogError(e, $"WS服务{Url.ToString()}连接失败");
            }
        }

        public async Task<OperateResult<string>> SendRequestAsync(string msg, int timeout = 900, bool isRetry = false)
        {
            if (SessionId == null)
            {
                await GetSession();
            }
            var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));
            OperateResult<string> result = new OperateResult<string> { IsSuccess = false };
            try
            {
                await ConnectAsync();
                msg = ReplaceSessionId(msg);
                byte[] buffer = Encoding.UTF8.GetBytes(msg);

                //准备接收响应数据
                var responseBuffer = new byte[1024];
                WebSocketReceiveResult res;
                StringBuilder sb = new StringBuilder();
                var needRetry = false;

                //此处将 发送数据与接收响应数据封装到一起
                //为了防止并发问题粘包 引入SemaphoreSlim
                await semaphore.WaitAsync();
                try
                {
                    await Client.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, cts.Token);
                    do
                    {
                        res = await Client.ReceiveAsync(new ArraySegment<byte>(responseBuffer), cts.Token);
                        if (res.MessageType == WebSocketMessageType.Text)
                        {
                            sb.Append(Encoding.UTF8.GetString(responseBuffer, 0, res.Count));
                        }
                        else if (res.MessageType == WebSocketMessageType.Binary)
                        {
                            //二进制数据 SCADA不用 所以此处未实现
                            Console.WriteLine("Binary data");
                        }
                        else if (res.MessageType == WebSocketMessageType.Close)
                        {
                            await Client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cts.Token);
                            needRetry = true;
                            break;
                        }
                    }
                    while (!res.EndOfMessage && !cts.IsCancellationRequested);
                }
                catch (WebSocketException ex)
                {
                    Client.Dispose();
                    Client = null;
                    SysLogger.LogError(ex, $"WebSocket通讯异常");
                    needRetry = true;
                }
                catch (Exception e)
                {
                    SysLogger.LogError(e, $"WS服务{Url.ToString()}发送数据失败");
                    throw;
                }
                finally
                {
                    semaphore.Release();
                }

                var response = sb.ToString();
                // SCADA 特殊逻辑
                if (response.Contains("\"System.Exception\""))
                {
                    if (response.Contains("非法用户访问"))
                    {
                        GetSession();
                        needRetry = true;
                    }
                    else
                    {
                        SysLogger.LogError($"WS服务{Url.ToString()}获取数据异常，[{response}]");
                        needRetry = true;
                    }
                }

                if (needRetry && !isRetry)
                {
                    return await SendRequestAsync(msg, timeout, true);
                }

                result.Content = response;
                result.IsSuccess = !needRetry;
            }
            catch (Exception ex)
            {
                result.IsSuccess = false;
                result.Message = ex.Message;
            }
            return result;
        }

        private string ReplaceSessionId(string msg)
        {
            if (msg.Contains("*SimpleSnowId*"))
            {
                msg = msg.Replace("*SimpleSnowId*", SimpleSnowId.GenerateId().ToString());
            }
            if (msg.Contains("*SessionId*"))
            {
                msg = msg.Replace("*SessionId*", SessionId);
            }
            if (msg.Contains("*Guid*"))
            {
                msg = msg.Replace("*Guid*", Guid.NewGuid().ToString("D"));
            }
            return msg;
        }

        public void Dispose()
        {
            if (Client?.State == WebSocketState.Open)
            {
                Client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", _cts.Token).Wait();
                Client.Dispose();
            }
            _cts?.Dispose();
        }
    }
}